|
To access the contents, click the chapter and section titles.
Bug Proofing Visual Basic: A Guide to Error Handling and Prevention
(Publisher: John Wiley & Sons, Inc.)
Author(s): Rod Stephens
ISBN: 0471323519
Publication Date: 11/01/98
CHAPTER 6 Being Obvious
Chapter 1, Programming Philosophy, mentions that programs are written for people, not for computers. Computers do not read comments and do not care if the code is neatly aligned to show scope and structure. For all the computer cares, you could write your program in machine code with no comments or indentation. The computer mindlessly executes one instruction at a time without ever understanding what it is doing.
Code is written primarily so humans can read it. All the work you do to make the code legible is for the benefit of yourself and others who read the code later. With that in mind, it is self-evident that the code should be as obvious as possible. When a human reads the code, its intent should be immediately clear. The code should do what it looks like it does. If the code looks like it does one thing but it actually does another, it will be hard to understand and debug.
This chapter explains techniques you can use to make your code obvious. If you do a good job, a reader should be able to pick up your code and immediately understand what it does and how.
Dont Use Clever Tricks
Clever tricks may be interesting and some are even efficient, but they make it harder for a reader to understand your code. They make what the code is doing less obvious.
For example, here is a straightforward way to swap the values of two variables A and B:
Dim A As Integer
Dim B As Integer
Dim tmp As Integer
Do something here. Initialize A and B, etc.
:
Switch A and B.
tmp = A
A = B
B = tmp
I have seen programmers propose the following as a better version because it avoids allocating the temporary variable tmp.
Dim A As Integer
Dim B As Integer
Do something here. Initialize A and B, etc.
:
Switch A and B.
A = A Xor B
B = A Xor B
A = A Xor B
This trick is far from obvious. To see how this code works, you need to know these facts about the Xor operator:
- A Xor B = B Xor A
- (A Xor B) Xor C = A Xor (B Xor C)
- A Xor A = 0
- A Xor 0 = A
Knowing these facts, consider the steps in the previous code again. Suppose A and B originally have the values a and b. Then the first statement sets A to A Xor B, which is the same as a Xor b.
A = A Xor B
= a Xor b
In the second statement, B is set to A Xor B. The current value of A is a Xor b, so
B = A Xor B
= (a Xor b) Xor b
= a Xor (b Xor b)
= a Xor 0
= a
In the third statement, A is set to A Xor B. The current value of A is a Xor b and the current value of B is a, so
A = A Xor B
= (a Xor b) Xor a
= a Xor a Xor b
= 0 Xor b
= b
In the end, A = b and B = a so the values have been switched as desired.
This code is undoubtedly clever, but it is very confusing. Unless the reader has worked through similar examples before or has a lot of experience with the Xor operator, it makes no sense whatsoever.
This code even takes about 50 percent longer than the simpler version. Its only advantage is that it avoids allocating a single 2-byte variable. Two bytes are hardly worth the confusion this trick may cause.
If a clever trick does not make the code much faster, do not use it; use a straightforward implementation instead. If you later discover the routine is a big performance bottleneck and the trick is absolutely necessary, you can change the code later.
Document Tricks
If you must use a clever trick, document it thoroughly. Occasionally, you may find a situation in which a clever trick improves a routines performance so much that it is worth some risk of confusion. In that case, help future readers by explaining the trick in a comprehensive comment. Make the trick obvious.
Dont Write Routines with Side Effects
When a routine changes the values of its parameters in a way that is not central to the routines mission, the change is called a side effect. For example, suppose the OpenTable subroutine takes the name of a database table as a parameter and it opens the table. When it finishes, the routine changes its parameter to contain a string listing the tables fields separated by commas. This change is unrelated to the main task of opening the table, so it is a side effect.
Side effects can be very confusing. Because they are not central to the routines mission, the reader often does not expect them to occur. They are not obvious. They are similar to a magicians trick during which the left hand does something while the right hand distracts you. While the user concentrates on the routines main purpose, the side effect is unnoticed.
To prevent confusion, do not write routines that have side effects. Break confusing behavior into separate routines. For instance, you could break the previous OpenTable subroutine into an OpenTable subroutine that opens the table, and a separate ListFields function that returns a list of the fields in the table.
Note that every function that changes its parameters has side effects. The main purpose of a function is to calculate a return value. Changing a parameter is not central to calculating the return value, so it is a side effect.
Functions that have side effects can be even more confusing than subroutines. For example, the following function SideEffect takes an integer as a parameter, adds 1 to the parameter, and then returns twice the new value.
Private Function SideEffect(value As Long) As Long
value = value + 1
SideEffect = 2 * value
End Function
It is difficult to tell offhand what the values of A, B, and C are after the following code executes. Study the code for a moment and see what you think the new values are.
Dim A As Long
Dim B As Long
Dim C As Long
A = 10
B = SideEffect(A)
A = SideEffect(B)
C = SideEffect(A)
This code is not at all obvious. A reader could examine the code for several minutes and still not figure out the correct values. Did you get A = 47, B = 23, and C = 94? How long did it take you to calculate these values? Were you certain of your result?
The following code shows an even more confusing example.
A = 7
If (A > 10) And (SideEffect(A) < 100) Then
A = A * 3
Else
A = A * 2
End If
This code looks as if it executes the SideEffect function only if the value A is greater than 10. The reasoning is that, if A is less than or equal to 10, the Boolean expression is False no matter what value SideEffect returns, so the program does not invoke SideEffect.
Unfortunately, this is not the way Visual Basic evaluates expressions. The program calls the SideEffect function whether A is greater than 10 or not.
You can reduce confusion by placing functions with side effects on separate lines.
A = 7
If (A > 10) Then
If (SideEffect(A) < 100) Then
A = A * 3
Else
A = A * 2
End If
Else
A = A * 2
End If
This is still fairly confusing. A better solution is to rewrite the SideEffect function so it does not have any side effects.
|